Spring Boot集成Redis做缓存

Spring Cache抽象

自3.1版以来,Spring 提供了对现有Spring应用程序透明地添加缓存的支持。下面介绍几个常用的注解。

@Cacheable

cacheNames

1
2
@Cacheable("books")
public Book findBook(ISBN isbn) {...}

大多数情况下,只声明一个缓存,但注释允许指定多个名称,以便使用多个缓存。

1
2
@Cacheable({"books", "isbns"})
public Book findBook(ISBN isbn) {...}

Custom Key Generation Declaration

@Cacheable注解允许用户指定通过其关键属性生成key,可使用SpEL表达式来选择参数(或它们的嵌套属性)。

Spring Cache提供了一些供我们使用的SpEL上下文数据,通过#来引用,具体可查看Spring官网:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#cache-spel-context。

1
2
3
4
5
6
7
8
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

还可以定义一个自定义的keyGenerator来生成key

1
2
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

key和keyGenerator参数是互斥的,指定两者的操作将导致异常。

Synchronized caching

1
2
@Cacheable(cacheNames="foos", sync=true)
public Foo executeExpensiveOperation(String id) {...}

这是一个可选功能,默认为false,有的缓存库可能不支持它。一般框架提供的所有CacheManager实现都支持它。

Conditional caching

  • condition: 在执行方法前,condition的值为true,则缓存数据
  • unless :在执行方法后,判断unless ,如果值为true,则不缓存数据
  • conditon和unless可以同时使用,则此时只缓存同时满足两者的记录

@Cacheable支持通过condition判断是否进行缓存,该条件参数采用SpEL

1
2
@Cacheable(cacheNames="book", condition="#name.length() < 32")
public Book findBook(String name)

此外还可以使用unless,与condition不同,unless根据返回结果来判断是否缓存。

注意unless表示如果不,判断条件是相反的。例如,我们只想缓存平装书,不缓存精装书:

1
2
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
public Book findBook(String name)

支持java.util.Optional,仅在其存在时将其内容用作缓存值。注意,#result始终引用业务实体Book而不是Optional。

1
2
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result?.hardback")
public Optional<Book> findBook(String name)

@CachePut

需要更新缓存而不干扰方法执行的情况,可以使用@CachePut注释,它支持与@Cacheable相同的参数。

如果使用 @Cacheable 注释,则当重复使用相同参数调用方法的时候,方法本身不会被调用执行,即方法本身被略过了,取而代之的是方法的结果直接从缓存中找到并返回了。

现实中并不总是如此,有些情况下我们希望方法一定会被调用,因为其除了返回一个结果,还做了其他事情,例如记录日志,调用接口等,这个时候,我们可以用 @CachePut 注释,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中。

1
2
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)

注意,对同一方法使用@CachePut和@Cacheable注释是不鼓励的,因为它们具有不同的行为。

@CacheEvict

注解@CacheEvict 定义了执行缓存删除的方法,它参数与@Cacheable类似,此外具有一个额外的参数allEntries,它表示是否需要删除命名空间下所有的缓存,而不仅仅是基于key的一条,默认为false :

1
2
@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)

@Caching

@Caching允许在同一个方法上使用多个嵌套的@Cacheable@CachePut@CacheEvict

1
2
3
4
5
6
7
8
9
10
11
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

@Caching(
put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.name"),
@CachePut(value = "user", key = "#user.account")
}
)
public User updateUser(User user)

@CacheConfig

@CacheConfig 类级别的注解:如果我们在此注解中定义cacheNames,则此类中的所有方法上 @Cacheable的cacheNames默认都是此值。当然@Cacheable也可以重定义cacheNames的值

1
2
3
4
5
6
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {

@Cacheable
public Book findBook(ISBN isbn) {...}
}

Spring Boot集成Redis做缓存

Redis配置

配置application.properties,列出的均是redis配置的默认值

1
2
3
4
5
6
7
8
#REDIS (RedisProperties)
spring.redis.host=
spring.redis.password=
spring.redis.port=6379
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=100
spring.redis.pool.max-wait=-1

Spring Boot会执行RedisAutoConfiguration,自动初始化初始化RedisTemplate和StringRedisTemplate

当在配置类(@Configuration)上使用@EnableCaching注解时,Spring Boot就会自动配置缓存基础架构。

1
2
3
4
@Configuration
@EnableCaching
public class AppConfig {
}

如果Redis可用且已配置,Spring Boot会自动配置RedisCacheManager。

Spring Boot初始化RedisCacheManager的过程

缓存管理接口org.springframework.cache.CacheManager,Spring Boot就是通过此类实现缓存的管理。redis对应此接口的实现类是org.springframework.data.redis.cache.RedisCacheManager。下面看看此类如何生成。

在配置文件application.properties的配置spring.redis.* 属性后,Spring Boot通过RedisAutoConfiguration来初始化RedisTemplate和StringRedisTemplate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {

/**
* Standard Redis configuration.
*/
@Configuration
protected static class RedisConfiguration {

@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

@Bean
@ConditionalOnMissingBean(StringRedisTemplate.class)
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

}

}

然后RedisCacheConfiguration会将RedisAutoConfiguration生成的RedisTemplate注入方法生成一个默认的RedisCacheManager 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* Redis cache configuration.
*
* @author Stephane Nicoll
* @since 1.3.0
*/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

private final CacheProperties cacheProperties;

private final CacheManagerCustomizers customizerInvoker;

RedisCacheConfiguration(CacheProperties cacheProperties,
CacheManagerCustomizers customizerInvoker) {
this.cacheProperties = cacheProperties;
this.customizerInvoker = customizerInvoker;
}

@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setUsePrefix(true);
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}

}

自定义RedisCacheManager

如果CacheManager是由Spring Boot自动配置的,那么可以通过公开一个实现CacheManagerCustomizer接口的bean来进行其他配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Bean
public CacheManagerCustomizer<RedisCacheManager> redisManagerCustomizer() {
return new CacheManagerCustomizer<RedisCacheManager>() {
@Override
public void customize(RedisCacheManager cacheManager) {
//自定义设置
//启用前缀
cacheManager.setUsePrefix(true);
//设置前缀
RedisCachePrefix cachePrefix = new DefaultRedisCachePrefix("redis_");
cacheManager.setCachePrefix(cachePrefix );

//设置过期时间为24小时,默认是不过期
cacheManager.setDefaultExpiration(24*60*60);
}
};
}